function mean(arr) {
  return arr.reduce((sum, cur) => sum + cur, 0) / arr.length;
}

function reduceByKey(tuples, reduceFn) {
  const grouped = {};
  const result = [];
  tuples.forEach(tuple => {
    if (grouped[tuple[0]] === undefined) {
      grouped[tuple[0]] = [];
    }
    grouped[tuple[0]].push(tuple[tuple[1]]);
  });
  Object.keys(grouped).forEach(
    (key, index) => (result[index] = [key, grouped[key].reduce(reduceFn)])
  );
  return result;
}

/**
 * @param {Array} arr 数组
 * @param {Function} fn return key
 */
function groupBy(arr, fn) {
  const grouped = {};
  arr.forEach(item => {
    const key = typeof fn === 'function' ? fn(item) : item[fn];
    if (grouped[key] === undefined) {
      grouped[key] = [];
    }
    grouped[key].push(item);
  });
  return grouped;
}

const arrayUtils = {
  append(arr, ...elements) {
    arr.push(...elements);
    return arr;
  },
  prepend(arr, ...elements) {
    arr.unshift(...elements);
    return arr;
  },
  insert(arr, index, ...elements) {
    arr.splice(index, 0, ...elements);
    return arr;
  },
  remove(arr, index) {
    arr.splice(index, 1);
    return arr;
  },
};

function maxBy(arr, prop) {
  let max = arr[0];
  arr.forEach(item => {
    if (item[prop] > max[prop]) max = item;
  });
  return max;
}

function minBy(arr, prop) {
  let min = arr[0];
  arr.forEach(item => {
    if (item[prop] <= min[prop]) min = item;
  });
  return min;
}

function toPairs(obj) {
  return Object.keys(obj).map(key => [key, obj[key]]);
}

function fromPairs(arr) {
  const obj = {};
  arr.forEach(item => (obj[item[0]] = item[1]));
  return obj;
}

function applyColumn(colDataset, columnName) {
  if (!colDataset.hasOwnProperty(columnName)) {
    colDataset[columnName] = [];
  }
  return colDataset;
}

function toColData(rowDataArr) {
  let result = {};
  rowDataArr.forEach(obj => {
    const colNames = Object.keys(obj);
    colNames.forEach(colName => {
      result = applyColumn(result, colName);
      result[colName].push(obj[colName]);
    });
  });
  return result;
}

function toRowData(colDataObj) {
  const result = [];
  const keys = Object.keys(colDataObj);
  const len = max(keys.map(key => colDataObj[key].length));
  for (let i = 0; i < len; i++) {
    result[i] = {};
    keys.forEach(key => {
      result[i][key] = colDataObj[key][i];
    });
  }
  return result;
}

function gcd(a, b) {
  if (b === 0) return a;
  return gcd(b, a % b);
}

function max(arr) {
  return Math.max.call(...arr);
}

function min(arr) {
  return Math.min.call(...arr);
}

// {key:[{k1:v1, k2:v2...}]} + 'k1' -> {key: [v1...]}
function mapValues(obj, fn) {
  const result = {};
  Object.keys(obj).forEach(key => {
    result[key] = obj[key].reduce((sum, cur) => {
      const value = typeof fn === 'function' ? fn(cur) : cur[fn];
      return sum.concat(value);
    }, []);
  });
  return result;
}

// Array prototype func
Array.prototype.groupBy = function (fn) {
  return groupBy(this, fn);
};

Array.prototype.maxBy = function (prop) {
  return maxBy(this, prop);
};

Array.prototype.minBy = function (prop) {
  return minBy(this, prop);
};
